using System;
using System.Collections.Generic;
using System.Text;

namespace RapidHDL
{
    public class HardwareScript
    {
        SortedDictionary<double, HWScriptCommand> dicScript;
        string sTranscript;

        RapidHardware oRapidHardware;
        double dLastClock = 0;
        bool bCompleted = false;

        string sName;
        bool bNoReset = false;

        public bool NoReset
        {
            get { return bNoReset; }
            set { bNoReset = value; }
        }

        public HardwareScript(RapidHardware poRapidHardware, string psName)
        {            
            oRapidHardware = poRapidHardware;
            dicScript = new SortedDictionary<double,HWScriptCommand>();
            sName = psName;
        }

        public void SignalOut(int piClockTime, string psSignalSourceName, string psValue, SignalFormat peSignalFormat)
        {
            HWScriptSignalOut oCommand = new HWScriptSignalOut(oRapidHardware, piClockTime, psSignalSourceName, psValue,peSignalFormat);
            AddScript(oCommand.ScheduleTime, (HWScriptCommand)oCommand);
        }

        public void SignalOut(int piClockTime, string psSignalSourceName, string psValue)
        {
            HWScriptSignalOut oCommand = new HWScriptSignalOut(oRapidHardware, piClockTime, psSignalSourceName, psValue);
            AddScript(oCommand.ScheduleTime, (HWScriptCommand)oCommand);
        }

        public void SignalCheck(int piClockTime, string psSignalViewName, string psValue, SignalFormat peSignalFormat)
        {
            HWScriptSignalCheck oCommand = new HWScriptSignalCheck(oRapidHardware, piClockTime, psSignalViewName, psValue, peSignalFormat);
            AddScript(oCommand.ScheduleTime, (HWScriptCommand)oCommand);
        }

        public void SignalCheck(int piClockTime, string psSignalViewName, string psValue)
        {
            HWScriptSignalCheck oCommand = new HWScriptSignalCheck(oRapidHardware, piClockTime, psSignalViewName, psValue);
            AddScript(oCommand.ScheduleTime, (HWScriptCommand)oCommand);
        }

        public void SignalView(int piClockTime, string psSignalViewName, SignalFormat peSignalFormat, string psFilterValue)
        {
            HWScriptSignalView oCommand = new HWScriptSignalView(oRapidHardware, piClockTime, psSignalViewName, peSignalFormat,psFilterValue);
            AddScript(oCommand.ScheduleTime, (HWScriptCommand)oCommand);
        }

        public void SignalView(int piClockTime, string psSignalViewName, SignalFormat peSignalFormat)
        {
            HWScriptSignalView oCommand = new HWScriptSignalView(oRapidHardware, piClockTime, psSignalViewName, peSignalFormat);
            AddScript(oCommand.ScheduleTime, (HWScriptCommand)oCommand);
        }

        public void SignalView(int piClockTime, string psSignalViewName)
        {
            HWScriptSignalView oCommand = new HWScriptSignalView(oRapidHardware, piClockTime, psSignalViewName);
            AddScript(oCommand.ScheduleTime, (HWScriptCommand)oCommand);
        }

        public void Echo(int piClockTime, string psMessage)
        {
            HWScriptEcho oCommand = new HWScriptEcho(oRapidHardware, piClockTime, psMessage);
            AddScript(oCommand.ScheduleTime, (HWScriptCommand)oCommand);
        }

        public void Pause(int piClockTime)
        {
            HWScriptPause oCommand = new HWScriptPause(oRapidHardware, piClockTime);
            AddScript(oCommand.ScheduleTime, (HWScriptCommand)oCommand);
        }

        public void AddScript(double pdScheduleTime, HWScriptCommand poCommand)
        {
            double dScheduleTime = pdScheduleTime;
            while (dicScript.ContainsKey(dScheduleTime))
            {
                dScheduleTime += .0000001;
            }
            dicScript.Add(dScheduleTime, poCommand);
        }

        public void WriteVerilogTestBench(int piIdx)
        {
            ScriptHelper oScript = new ScriptHelper(oRapidHardware);

            int iSourceCount = oRapidHardware.SystemInterface.SignalSources.Count - 1;
            int iViewCount = oRapidHardware.SystemInterface.SignalViews.Count - 1;

            if (iSourceCount + iViewCount <= 0)
                return;

            if (oRapidHardware.Settings.UseFIFO || oRapidHardware.Settings.UseSDRAM || oRapidHardware.Settings.UseTestBus)
            {
                return;
                //throw new Exception("UseFIFO, UseSDRAM, UseTestBus settings are not supported.");
            }

            string sName = "TEST_BENCH";
            if (piIdx > 1)
                sName += "_" + piIdx.ToString();
            oScript.NewFile(sName + ".v");

            oScript.WriteLine("module " + sName + ";");
            oScript.BlankLine();

            string sVerilog = "";

            sVerilog = oScript.BuildString(sVerilog, "reg CLK_I;", 1);
            if (oRapidHardware.Settings.UseReset)
            {
                sVerilog = oScript.BuildString(sVerilog, "reg RESET_I;", 1);
            }

            foreach (SignalSource oSignal in oRapidHardware.SystemInterface.SignalSources.Values)
            {
                if (!oSignal.IsIOTest)
                    sVerilog = oScript.BuildString(sVerilog, "reg " + oScript.VerilogVector(oSignal.NameHDL, oSignal.Width) + ";", 1);
            }

            foreach (SignalView oSignal in oRapidHardware.SystemInterface.SignalViews.Values)
            {
                if (!oSignal.IsIOTest)
                    sVerilog = oScript.BuildString(sVerilog, "wire " + oScript.VerilogVector(oSignal.NameHDL, oSignal.Width) + ";", 1);
            }
            oScript.WriteLine(sVerilog);

            oScript.BlankLine();
            
            oScript.BlankLine();
            oScript.WriteLine("TOP top");
            sVerilog = "(";

            sVerilog = oScript.BuildString(sVerilog, ".CLK_I(CLK_I),", 1);
            if (oRapidHardware.Settings.UseReset)
                sVerilog = oScript.BuildString(sVerilog, ".RESET_I(RESET_I),", 1);
            foreach (SignalSource oSignal in oRapidHardware.SystemInterface.SignalSources.Values)
            {
                if (!oSignal.IsIOTest)
                {
                    sVerilog = oScript.BuildString(sVerilog, "." + oSignal.NameHDL + "(" + oSignal.NameHDL + "),", 1);
                }
            }
            foreach (SignalView oSignal in oRapidHardware.SystemInterface.SignalViews.Values)
            {
                if (!oSignal.IsIOTest)
                {
                    NodeVector oTopLevelOutput = oSignal.GetTopLevelComponentOutput();
                    sVerilog = oScript.BuildString(sVerilog, "." + oSignal.NameHDL + "(" + oSignal.NameHDL + "),", 1);
                }
            }
            sVerilog = oScript.RemoveStringEnding(sVerilog, 1);
            oScript.WriteLine(sVerilog);
            oScript.WriteLine(" );");
            oScript.BlankLine();

            oScript.WriteLine("always begin");
            oScript.WriteLine("#1 CLK_I = !CLK_I;", 1);
            oScript.WriteLine("end");
            oScript.BlankLine();

            oScript.WriteLine("initial begin");
            oScript.WriteLine("$dumpfile(\"TEST_BENCH.vcd\");", 1);
            oScript.WriteLine("$dumpvars(0,CLK_I);", 1);
            if (oRapidHardware.Settings.UseReset)
                oScript.WriteLine("$dumpvars(0,RESET_I);", 1);
            foreach (SignalSource oSignal in oRapidHardware.SystemInterface.SignalSources.Values)
            {
                if (!oSignal.IsIOTest)
                {
                    oScript.WriteLine("$dumpvars(0," + oSignal.NameHDL + ");", 1);
                }
            }
            foreach (SignalView oSignal in oRapidHardware.SystemInterface.SignalViews.Values)
            {
                if (!oSignal.IsIOTest)
                {
                    oScript.WriteLine("$dumpvars(0," + oSignal.NameHDL + ");", 1);
                }
            }

            foreach (SignalSource oSignal in oRapidHardware.SystemInterface.SignalSources.Values)
            {
                if (!oSignal.IsIOTest)
                {
                    oScript.WriteLine(oSignal.NameHDL + "=" + oSignal.Width.ToString() + "'b" + oSignal.ValueAsString + ";",1);
                }
            }


            oScript.WriteLine("CLK_I=1;",1);

            double dResetOffset = 0;
            if (oRapidHardware.Settings.UseReset)
            {
                oScript.WriteLine("RESET_I=0;", 1);
                if (!bNoReset)
                {
                    oScript.WriteLine("#2 RESET_I=1;", 1);
                    oScript.WriteLine("#8 RESET_I=0;", 1);
                    dResetOffset = 10.0;
                }
            }


            double dTimeLast = 0.0;
            foreach(KeyValuePair<double,HWScriptCommand> kpEntry in dicScript)
            {
                double dDelay = kpEntry.Key - dTimeLast;
                string sTime = "";
                if (dDelay > 0)
                {
                    double dScaledDelay = dDelay * 2;
                    sTime = "#" + dScaledDelay.ToString() + " ";
                }
                dTimeLast = kpEntry.Key;
                HWScriptCommand oCommand = kpEntry.Value;
                string sCommand = oCommand.ToVerilog();
                if (sCommand != "")
                    oScript.WriteLine(sTime + sCommand + ";", 1);
            }
            oScript.WriteLine("$finish();",1);
            oScript.WriteLine("end");

            oScript.BlankLine();
            oScript.WriteLine("endmodule");
            oScript.CloseFile();

            oScript.NewFile(sName + ".scr");
            List<string> lDependencies = oRapidHardware.Structure.TopLevelComponent.ComponentVerilog.GetDependencyList();
            foreach (string sFile in lDependencies)
            {
                oScript.WriteLine(sFile + ".v");
            }
            oScript.WriteLine("TOP.v");
            oScript.WriteLine(sName + ".v");
            oScript.CloseFile();

            oScript.NewFile("msim.do");
            foreach (string sFile in lDependencies)
            {
                oScript.WriteLine("vlog -reportprogress 300 -work work {" + sFile + ".v}");
            }
            oScript.WriteLine("vlog -reportprogress 300 -work work {TOP.v}");
            oScript.WriteLine("vlog -reportprogress 300 -work work {" + sName + ".v}");
            oScript.WriteLine("vsim -c " + sName + " -novopt");
            oScript.WriteLine("view wave");
            oScript.WriteLine("add wave /TEST_BENCH/top/*");
            double dStopTime = dTimeLast * 2 + 2 + dResetOffset;
            oScript.WriteLine("run " + dStopTime.ToString());

            oScript.CloseFile();
            

        }

        public void WriteSynthesisVerilogTestBench(int piIdx)
        {
            string sName = "TEST_BENCH_SYNTH";
            if (piIdx > 1)
                sName += "_" + piIdx.ToString();

            ScriptHelper oScript = new ScriptHelper(oRapidHardware);
            oScript.NewFile(sName + ".scr");
            oScript.WriteLine("adk.v");
            oScript.WriteLine("TOP_SYNTH.v");
            oScript.WriteLine("TEST_BENCH.v");
            oScript.CloseFile();

        }

        
        public string SimulateVerilogTestBench(int piIdx)
        {
            string sName = "TEST_BENCH";
            if (piIdx > 1)
                sName += "_" + piIdx.ToString();

            string sResult = XilinxTools.LaunchIcarusSim(oRapidHardware.Settings.IcarusPath, oRapidHardware.Settings.OutputPath, sName);
            if (sResult != "")
                sResult = "Simulation Failed for " + sName + "\n" + sResult;
            return sResult;
        }

        public string SimulateSynthesisVerilogTestBench(int piIdx)
        {
            string sName = "TEST_BENCH_SYNTH";
            if (piIdx > 1)
                sName += "_" + piIdx.ToString();

            string sResult = XilinxTools.LaunchIcarusSim(oRapidHardware.Settings.IcarusPath, oRapidHardware.Settings.OutputPath, sName);
            if (sResult != "")
                sResult = "Simulation Failed for " + sName + "\n" + sResult;
            return sResult;
        }


        public bool DoScript(bool pbResume)
        {
            return DoScript(pbResume,false);
        }

        public bool DoScript(bool pbResume, bool pbNoPause)
        {
            double dStartTime = 0;

            sTranscript = "";

            if (!pbResume || bCompleted || dLastClock == 0)
            {
                dStartTime = 0;
                dLastClock = 0;
                bCompleted = false;
                sTranscript = "Starting Hardware Script: " + sName + "\n";
                if (!bNoReset)
                {
                    oRapidHardware.SystemInterface.SystemControl.Reset();
                    sTranscript += "    with reset.. \n";
                }
                else
                    sTranscript += "    with no reset.. \n";

            }
            else
            {
                dStartTime = dLastClock + .0001;
            }

            int iClockCycles = 0;
            foreach (double dTime in dicScript.Keys)
            {
                iClockCycles = (int)dTime - (int)dLastClock;

                if (iClockCycles > 0)
                {
                    // run the clock
                    oRapidHardware.SystemInterface.SystemControl.Go(iClockCycles);
                    dLastClock = dTime;
                }

                if (dTime > dStartTime)
                {
                    HWScriptCommand oCommand = dicScript[dTime];
                    string sResult = oCommand.DoCommand();
                    if (sResult.Length > 0)
                    {
                        sTranscript += (oCommand.ClockTime.ToString() + " : " + sResult + "\n");
                        if (sResult.Contains("ERROR:"))
                            return false;
                        if (sResult == "PAUSE")
                        {
                            dLastClock = dTime;
                            if (!pbNoPause)
                                return true;

                        }
                    }
                }
            }
            sTranscript += "Completed Script: " + sName + "\n";
            bCompleted = true;
            return true;
        }


        public void WriteMachTA(int piIdx)
        {
            ScriptHelper oScript = new ScriptHelper(oRapidHardware);

            int iSourceCount = oRapidHardware.SystemInterface.SignalSources.Count - 1;
            int iViewCount = oRapidHardware.SystemInterface.SignalViews.Count - 1;

            if (iSourceCount + iViewCount <= 0)
                return;

            if (oRapidHardware.Settings.UseFIFO || oRapidHardware.Settings.UseSDRAM || oRapidHardware.Settings.UseTestBus)
            {
                return;
                //throw new Exception("UseFIFO, UseSDRAM, UseTestBus settings are not supported.");
            }

            string sName = "mta";
            if (piIdx > 1)
                sName += "_" + piIdx.ToString();
            oScript.NewFile(sName + ".do");

            string sOutput = "";

            oScript.WriteLine("plot V(CLK_I)");
            if (oRapidHardware.Settings.UseReset)
            {
                oScript.WriteLine("plot V(RESET_I)");
            }

            foreach (SignalSource oSignal in oRapidHardware.SystemInterface.SignalSources.Values)
            {
                if (!oSignal.IsIOTest)
                {
                    if (oSignal.Width > 1)
                    {
                        for (int iBit = oSignal.Width - 1; iBit >= 0; iBit--)
                        {
                            oScript.WriteLine("plot V(" + oSignal.NameHDL + "[" + iBit.ToString() + "])");
                        }
                    }
                    else
                        oScript.WriteLine("plot V(" + oSignal.NameHDL + ")");
                }
            }

            foreach (SignalView oSignal in oRapidHardware.SystemInterface.SignalViews.Values)
            {
                if (!oSignal.IsIOTest)
                {
                    if (oSignal.Width > 1)
                    {
                        for (int iBit = oSignal.Width - 1; iBit >= 0; iBit--)
                        {
                            oScript.WriteLine("plot V(" + oSignal.NameHDL + "[" + iBit.ToString() + "])");
                        }
                    }
                    else
                        oScript.WriteLine("plot V(" + oSignal.NameHDL + ")");
                }
            }

            sName = "mta_rom";
            if (piIdx > 1)
                sName += "_" + piIdx.ToString();

            oScript.WriteLine("run -tvend -dc " + sName + ".tv");
            oScript.CloseFile();


            Dictionary<string, string> dssLastValue = new Dictionary<string, string>();
            oScript.NewFile(sName + ".tv");

            oScript.WriteLine("CODEFILE");
            oScript.WriteLine("UNITS ns");
            oScript.WriteLine("RISE_TIME .5");
            oScript.WriteLine("FALL_TIME .5");

            string sInputs = "CLK_I";

            if (oRapidHardware.Settings.UseReset)
            {
                sInputs += ",RESET_I";
                dssLastValue.Add("RESET_I", "1");                
            }

            foreach (SignalSource oSignal in oRapidHardware.SystemInterface.SignalSources.Values)
            {
                if (!oSignal.IsIOTest)
                {
                    dssLastValue.Add(oSignal.NameHDL,oSignal.ValueAsString);

                    if (oSignal.Width == 1)
                    {
                        sInputs += "," + oSignal.NameHDL;
                    }
                    else
                    {
                        for (int iBit = oSignal.Width - 1; iBit >= 0; iBit--)
                            sInputs += "," + oSignal.NameHDL + "[" + iBit.ToString() + "]";
                    }
                }
            }
            oScript.WriteLine("INPUTS " + sInputs + ";");


            string sOutputs = "";
            foreach (SignalView oSignal in oRapidHardware.SystemInterface.SignalViews.Values)
            {
                if (!oSignal.IsIOTest)
                {
                    dssLastValue.Add(oSignal.NameHDL, new string('X', oSignal.Width));

                    if (oSignal.Width == 1)
                    {
                        sOutputs += "," + oSignal.NameHDL;
                    }
                    else
                    {
                        for (int iBit = oSignal.Width - 1; iBit >= 0; iBit--)
                            sOutputs += "," + oSignal.NameHDL + "[" + iBit.ToString() + "]";
                    }
                }
            }

            if (sOutputs.Length > 0)
            {
                oScript.WriteLine("OUTPUTS " + sOutputs.Substring(1, sOutputs.Length - 1) + ";");
            }

            oScript.WriteLine("CODING(ROM)");

            
            int iClock = 0;
            int iStopTime = 0;
            int iResetOffset = 0;
            string sLastInput = "";


            if (oRapidHardware.Settings.UseReset)
            {
                dssLastValue["RESET_I"] = "1";
                sLastInput = WriteMTALine(0, 4, oRapidHardware.Settings.ClockPeriodNs / 2, dssLastValue, oScript, sLastInput);
                dssLastValue["RESET_I"] = "0";
                sLastInput = WriteMTALine(4, 5, oRapidHardware.Settings.ClockPeriodNs / 2, dssLastValue, oScript, sLastInput);
                iResetOffset = 5;
            }

            foreach(KeyValuePair<double,HWScriptCommand> oEntry in dicScript)
            {
                double dClock = oEntry.Key;
                HWScriptCommand oCommand = oEntry.Value;

                string sSignalName = oCommand.SignalNameHDL();
                string sSignalValue = oCommand.SignalValue();
                int iTime = System.Convert.ToInt32(dClock);

                if (sSignalName != "")
                {

                    if (iTime > iClock)
                    {
                        sLastInput = WriteMTALine(iClock + iResetOffset, iTime + iResetOffset, oRapidHardware.Settings.ClockPeriodNs / 2, dssLastValue, oScript, sLastInput);
                        iClock = iTime;
                    }
                    dssLastValue[sSignalName] = sSignalValue;
                }
                iStopTime = iTime;
            }

            WriteMTALine(iClock + iResetOffset, iStopTime + 1 + iResetOffset, oRapidHardware.Settings.ClockPeriodNs / 2, dssLastValue, oScript,sLastInput);

            oScript.WriteLine("END");
            oScript.CloseFile();
            TextFile.ConvertToUnix(oRapidHardware.Settings.OutputPath + sName + ".tv");
            sName = "mta";
            if (piIdx > 1)
                sName += "_" + piIdx.ToString();
            TextFile.ConvertToUnix(oRapidHardware.Settings.OutputPath + sName + ".do");
        }

        private string WriteMTALine(int piClockNow, int piClockStop, int piClockHalfPeriod, Dictionary<string, string> pdssValues, ScriptHelper poScript, string psLastInput)
        {
            string sLastInput = psLastInput;
            // set the values
            for (int iClock = piClockNow; iClock < piClockStop; iClock++)
            {
                string sVector = "";
                string sInputs = "";
                string sOutputs = "";

                if (pdssValues.ContainsKey("RESET_I"))
                {
                    sInputs += pdssValues["RESET_I"];
                }
                
                foreach (SignalSource oSignal in oRapidHardware.SystemInterface.SignalSources.Values)
                {
                    if (!oSignal.IsIOTest)
                    {
                        string sValue = pdssValues[oSignal.NameHDL];
                        sInputs += sValue;
                    }
                }

                sOutputs = "";
                foreach (SignalView oSignal in oRapidHardware.SystemInterface.SignalViews.Values)
                {
                    if (!oSignal.IsIOTest)
                    {
                        string sValue = pdssValues[oSignal.NameHDL];
                        sOutputs += sValue;
                    }
                }

                int iMTATime;


                sVector = "<1" + sLastInput + ">" + sOutputs + ";";
                iMTATime = iClock * 2 * piClockHalfPeriod - 2;
                if (iMTATime > 0)
                    poScript.WriteLine("@" + iMTATime.ToString() + " " + sVector);

                sLastInput = sInputs;
                iMTATime = iClock * 2 * piClockHalfPeriod;
                sVector = "<0" + sInputs + ">";
                sVector += new string('X', sOutputs.Length);
                sVector += ";";
                poScript.WriteLine("@" + iMTATime.ToString() + " " +  sVector);


                sVector = "<1" + sInputs + ">";

                iMTATime = iClock * 2 * piClockHalfPeriod + piClockHalfPeriod;

                foreach (SignalView oSignal in oRapidHardware.SystemInterface.SignalViews.Values)
                {
                    if (!oSignal.IsIOTest)
                    {
                        string sValue = pdssValues[oSignal.NameHDL];
                        sValue = new string('X', sValue.Length);
                        sVector += sValue;
                        pdssValues[oSignal.NameHDL] = sValue;
                    }
                }
                sVector += ";";
                poScript.WriteLine("@" + iMTATime.ToString() + " " + sVector);
            }
            return sLastInput;

        }


        public string Transcript
        {
            get
            {
                return sTranscript;
            }
        }

        public bool Completed
        {
            get
            {
                return bCompleted;
            }
        }

        public string Name
        {
            get { return sName; }
        }

        // need a better way to hook in...
        //   pause, signals, displays, etc.
        //   implement script item as a class, override the class for each version
        //
        // need to add signals viewable by waveform
        //
        // add our own waveform viewer?  or just generate vcd file
        //
        // need to generate verilog build file
        //  need to call verilog compiler and catch errors
        //
        // need to invoke gtk wave
        // 
        // pli interface?
        //
        // don't error if signal doesn't exist...


    }
}
